<?php

	define('N2F_EVT_REQUEST_URL_START',	'n2f_evt_request_url_start');
	define('N2F_EVT_REQUEST_URL_FINISH',	'n2f_evt_request_url_finish');

	define('REQUEST_TYPE_POST',	'POST');
	define('REQUEST_TYPE_GET',	'GET');
	define('REQUEST_TYPE_PUT',	'PUT');

	class n2f_http_request extends n2f_events {

		/**
		 * The URL which we will be requesting.
		 *
		 * @var string|null
		 */
		public $url;

		/**
		 * The type of request which will be sent. (POST|GET)
		 *
		 * @var string
		 */
		public $type;

		/**
		 * The query parameters which will be sent
		 * to the url through either post or get.
		 *
		 * @var array
		 */
		public $params = array();

		/**
		 * The headers which will be sent to the request url.
		 *
		 * @var string
		 */
		public $headers = array();

		/**
		 * The amount of seconds to wait until the request is timed out.
		 *
		 * @var int
		 */
		public $timeout = 60;

		/**
		 * The response which was returned by the executed request.
		 *
		 * @var string|null
		 */
		public $response = null;

		/**
		 * Initialize a new url request.
		 *
		 * @param string|null $url
		 * @param array $params
		 * @param string $type
		 * @param string|array $header
		 */
		public function __construct($url = null, $type = REQUEST_TYPE_GET, $params = array(), $timeout = 60, $headers = array()) {
			$this->url	= $url;
			$this->params	= $params;
			$this->type	= $type;
			$this->timeout	= $timeout;
			$this->headers	= $headers;
		}

		public function addParams($params) {
			if( !is_array($params) )
				return false;

			if(count($params) > 0)
				foreach($params as $name => $value)
					$this->addParam($name, $value);

			return true;
		}

		/**
		 * Add a new parameter to the list of parameters.
		 *
		 * @param string $key
		 * @param string|int $value
		 *
		 * @return bool
		 */
		public function addParam($key, $value) {
			if( empty($key) || empty($value) )
				return false;

			$this->params[$key] = $value;

			return true;
		}

		/**
		 * Adds another header to the list.
		 *
		 * @param array $header
		 *
		 * @return bool
		 */
		public function addHeaders($headers) {
			if( !is_array($headers) )
				return false;

			if( count($headers) > 0)
				foreach($headers as $head => $value)
					$this->addHeader($head, $value);

			return true;
		}

		/**
		 * Adds a single header to the list.
		 *
		 * @param $name
		 * @param $value
		 *
		 * @return bool
		 */
		public function addHeader($name, $value) {
			if( empty($name) || empty($value) )
				return false;

			$this->headers[$name] = $value;

			return true;
		}

		/**
		 * Find the best way to execute the request, execute it,
		 * and then return the response object.
		 *
		 * @return n2f_response|null
		 */
		public function execute() {

			$this->hitEvent(N2F_EVT_REQUEST_URL_START, array(&$this));

			if($this->_hasCurl())
				$this->response = $this->_curl();

			else if($this->_hasFget())
				$this->response = $this->_fget();

			$this->hitEvent(N2F_EVT_REQUEST_URL_FINISH, array(&$this->response));

			return $this->response;
		}

		/**
		 * Tells whether the curl extension is loaded.
		 *
		 * @return bool
		 */
		private function _hasCurl() {
			return (in_array('curl', get_loaded_extensions()) ? true : false);
		}

		/**
		 * Tells whether the file_get_contents function exists.
		 *
		 * @return bool
		 */
		private function _hasFget() {
			return (function_exists('file_get_contents') ? true : false);
		}

		/**
		 * Executes the request using the cURL extension, and then
		 * returns the response.
		 *
		 * @return n2f_response|null
		 */
		private function _curl() {

			if(strtoupper($this->type) == REQUEST_TYPE_GET)
				$cURL = curl_init($this->url.'?'.$this->processParams($this->params));
			else $cURL = curl_init($this->url);

			if(!is_resource($cURL))
				return null;

			curl_setopt($cURL, CURLOPT_HTTPHEADER, $this->processHeaders($this->headers));

			$type = strtoupper($this->type);
			if($type == REQUEST_TYPE_POST) {
				curl_setopt($cURL, CURLOPT_POST, true);
				curl_setopt($cURL, CURLOPT_POSTFIELDS, $this->params);

			} else if($type == REQUEST_TYPE_PUT)
				curl_setopt($cURL, CURLOPT_PUT, true);

			curl_setopt($cURL, CURLOPT_HEADER, true);
			curl_setopt($cURL, CURLOPT_CONNECTTIMEOUT, $this->timeout);
			curl_setopt($cURL, CURLOPT_RETURNTRANSFER, true);

			$res = curl_exec($cURL);

			$response 			= new n2f_response($res, $this);
			$response->code		= curl_getinfo($cURL, CURLINFO_HTTP_CODE);
			$response->content		= curl_getinfo($cURL, CURLINFO_CONTENT_TYPE);
			$response->total_time	= curl_getinfo($cURL, CURLINFO_TOTAL_TIME);
			$response->upload_size	= curl_getinfo($cURL, CURLINFO_SIZE_UPLOAD);
			$response->download_size	= curl_getinfo($cURL, CURLINFO_SIZE_DOWNLOAD);

			if($error = curl_error($cURL))
				$response->errors[] = $error;

			curl_close($cURL);

			return $response;
		}

		private function _fget() {

		}

		/**
		 * Transforms the headers array into a string.
		 *
		 * @param array $headers
		 *
		 * @return array
		 */
		private function processHeaders($headers) {
			if(!is_array($headers) || count($headers) < 1)
				return array();

			$ret = array();
			foreach($headers as $head => $value)
				$ret[] = $head.': '.$value;

			return $ret;
		}

		/**
		 * Transform the parameters into a url encoded string.
		 *
		 * @param $params
		 *
		 * @return array|string
		 */
		private function processParams($params) {
			if(!is_array($params) || count($params) < 1)
				return array();

			$ret = '';
			foreach($params as $name => $value)
				$ret .= $name.'='.$value.'&';

			$ret = urlencode($params);

			return $ret;
		}
	}

	class n2f_response {

		/**
		 * The response from a request.
		 *
		 * @var string
		 */
		public $response;

		/**
		 * List of all the response headers.
		 *
		 * @var array
		 */
		public $headers;

		/**
		 * The request from which the response was created.
		 *
		 * @var n2f_http_request|null
		 */
		public $request;

		/**
		 * The HTTP Code that was received.
		 *
		 * @var string
		 */
		public $code;

		/**
		 * The Content-Type that was received from the response.
		 *
		 * @var string
		 */
		public $content;

		/**
		 * The total transfer time in seconds.
		 *
		 * @var int
		 */
		public $total_time;

		/**
		 * The number of bytes uploaded from the request.
		 *
		 * @var int
		 */
		public $upload_size;

		/**
		 * The number of bytes downloaded from the request.
		 *
		 * @var int
		 */
		public $download_size;

		/**
		 * A list of all the errors.
		 *
		 * @var array
		 */
		public $errors;

		/**
		 * Builds the response object.
		 *
		 * @param $response
		 * @param null $request
		 */
		public function __construct($response, $request = null) {
			$this->setResponse($response);

			if(get_class($request) == 'n2f_http_request' || is_subclass_of($request, 'n2f_http_request'))
				$this->request = $request;
		}

		/**
		 * Returns the response.
		 *
		 * @return string
		 */
		public function getResponse() {
			return $this->response;
		}

		/**
		 * This will set the response, after removing any header information
		 * and placing it into an array.
		 *
		 * @param string $response
		 */
		private function setResponse($response) {
			$len = stripos($response, "<!doctype");

			if($len > 0){
				$this->headers = $this->seperateHeaders(substr($response, 0, $len));
				$this->response = substr($response, $len, strlen($response));
			} else
				$this->response = $response;
		}

		/**
		 * Takes a string of headers, and transforms them into an array.
		 *
		 * @param string $headers
		 */
		private function seperateHeaders($headers) {
			$ret = array();

			$headers = explode("\r\n", $headers);

			for($i = 1; $i < count($headers)-2; $i++){
				$head = explode(':', $headers[$i]);
				$ret[trim($head[0])] = trim($head[1]);
			}

			return $ret;
		}

		/**
		 * Save the response to a file.
		 *
		 * @param string $file
		 */
		public function save($directory, $filename, $includeHeaders = false) {
			if(!is_dir($directory))
				if(!@mkdir($directory, 0755, true))
					return false;

			// We need to make sure there is a seperator
			// between the directory and filename.
			if($directory[count($directory-1)] != '/');
				$directory .= '/';

			if( !$file = @fopen($directory.$filename, 'w'))
				return false;

			$response = $this->response;

			if($includeHeaders) {
				$headers = '';
				foreach($this->headers as $name => $value)
					$headers .= $name.': '.$value."\r\n";

				$response = $headers.$response;
			}

			if(!@fwrite($file, $response, strlen($response))){
				fclose($file);

				return false;
			}

			fclose($file);

			return true;
		}

		/**
		 * Returns a single error, and then primes to the next.
		 *
		 * @return mixed
		 */
		public function getError() {
			if(count($this->errors) < 1)
				return false;

			$err = current($this->errors);

			next($this->errors);

			return $err;
		}
	}

?>